﻿using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.Uow;
using Abp.MultiTenancy;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Transactions;

namespace FwProject.EntityFrameworkCore.GloabDiagnosticInitializer
{
    /// <summary>
    /// 目前没有想到好的方法来使用这个对象后期想到好方法了在来使用吧读写分离 主要监控数据库的消息发布
    /// Diagnostic listener for Microsoft.EntityFrameworkCore.* events
    /// </summary>
    public class RelationalDiagnosticListener<TDbContext> : IProfilerDiagnosticListener where TDbContext : DbContext
    {
        // Maps to https://github.com/aspnet/EntityFramework/blob/f386095005e46ea3aa4d677e4439cdac113dbfb1/src/EFCore.Relational/Internal/RelationalDiagnostics.cs
        // See https://github.com/aspnet/EntityFramework/issues/7939 for info

        public RelationalDiagnosticListener(List<ConnnectionSwtich> list, IActiveUnitOfWork activeUnitOfWork) {
            ConnStringDictionary = list;
          
            this._activeUnitOfWork = activeUnitOfWork;
        }
        public RelationalDiagnosticListener() {
            List<ConnnectionSwtich> ConnStringDictionary = new List<ConnnectionSwtich>();
        }

        /// <summary>
        /// Diagnostic Listener name to handle
        /// </summary>
        public string ListenerName => "Microsoft.EntityFrameworkCore";

        /// <summary>
        /// 数据库链接字符串封装类
        /// </summary>
        List<ConnnectionSwtich> ConnStringDictionary { get; set; }
     

        IActiveUnitOfWork _activeUnitOfWork { get; set; }

        // Tracking currently open items, connections, and transactions, for logging upon their completion or error


        /// <summary>
        /// Notifies the observer that the provider has finished sending push-based notifications.
        /// </summary>
        public void OnCompleted() { }
        /// <summary>
        /// Notifies the observer that the provider has experienced an error condition.
        /// </summary>
        /// <param name="error">An object that provides additional information about the error.</param>
        public void OnError(Exception error) => Trace.WriteLine(error);

        private DbContext context = null;
        /// <summary>
        /// Provides the observer with new data.
        /// </summary>
        /// <param name="kv">The current notification information.</param>
        public void OnNext(KeyValuePair<string, object> kv)
        {
            var key = kv.Key;
            var val = kv.Value;
            if (key == RelationalEventId.CommandExecuting.Name)
            {
                if (val is CommandEventData data)
                {
                   
                    bool isDistributedTran = Transaction.Current != null &&
                                     Transaction.Current.TransactionInformation.Status !=
                                     TransactionStatus.Committed;
                    if (!isDistributedTran)
                    {
                        //有问题abp context 是属于内部管理无法重新添加新的context到原始内部中所以没办法重置一个新的context到系统中
                        //if (data.ExecuteMethod == DbCommandMethod.ExecuteReader)
                        //    ResetConnection(data.Command);
                        //if (data.ExecuteMethod == DbCommandMethod.ExecuteScalar)
                        //    ResetConnection(data.Command);

                    }
                }
            }
            else if (key == CoreEventId.QueryModelCompiling.Name)
            {
                if (val is QueryModelEventData data)
                {

                    // data.Connection.ConnectionString = "";
                }
            }
            else if (key == CoreEventId.ContextInitialized.Name)
            {
                if (val is ContextInitializedEventData data)
                {
                    context = data.Context;
                    // data.Connection.ConnectionString = "";
                }
            }
            else if (key == RelationalEventId.ValueConversionSqlLiteralWarning.Name)
            {
                if (val is ValueConverterEventData data)
                {
                 
                    // data.Connection.ConnectionString = "";
                }
            }
            else if (key == RelationalEventId.CommandExecuted.Name)
            {

            }
            else if (key == RelationalEventId.CommandError.Name)
            {

            }
            else if (key == RelationalEventId.DataReaderDisposing.Name)
            {

            }
            // TODO consider switching to ConnectionEndEventData.Duration
            // This isn't as trivial as it appears due to the start offset of the request
            else if (key == RelationalEventId.ConnectionOpening.Name)
            {
                if (val is ConnectionEventData data)
                {
                    
                   // data.Connection.ConnectionString = "";
                }

            }
            else if (key == RelationalEventId.ConnectionOpened.Name)
            {

            }
            else if (key == RelationalEventId.ConnectionClosing.Name)
            {

               
            }
            else if (key == RelationalEventId.ConnectionClosed.Name)
            {

            }
            else if (key == RelationalEventId.ConnectionError.Name)
            {

            }
            else if (key == RelationalEventId.TransactionDisposed.Name)
            {
                if (val is TransactionEventData data)
                {
                  
                  


                    // data.Connection.ConnectionString = "";
                }
            }
        }

        /// <summary>
        /// 这里的目的就是实现数据库链接字符串重新设置
        /// </summary>
        /// <param name="dbCommand"></param>
        /// <param name="connString"></param>
        private void ResetConnection(DbCommand dbCommand)
        {
            var dbTransaction = dbCommand.Transaction;
            if (dbCommand.Connection.State == ConnectionState.Open)
            {
                if (ConnStringDictionary.Count > 0)
                {
                    dbCommand.Connection.Close();
                    dbCommand.Connection.ConnectionString = ConnString();
                    dbCommand.Connection.Open();
                }
               
            }
            if (dbCommand.Connection.State == ConnectionState.Closed)
            {
                dbCommand.Connection.Open();
            }
        }
        private string ConnString() {
            string connString = string.Empty;
            try
            {
                Dictionary<long, string> dic = new Dictionary<long, string>();
                foreach (var item in ConnStringDictionary)
                {
                    if (item.connDataBaseEnmu != ConnDataBaseEnmu.Master)
                    {
                        try
                        {
                            Ping pingSender = new Ping();
                            PingReply reply = pingSender.Send(item.Ip, 120);
                            if (reply.Status == IPStatus.Success)
                            {
                                if(!dic.ContainsKey(reply.RoundtripTime))
                                dic.Add(reply.RoundtripTime,item.ConnString);
                            }
                        }
                        catch (Exception)
                        {

                            throw;
                        }

                    }
                }
                ///key排序
                var storByConn = dic.OrderBy(o => o.Key);
                if(storByConn.Count()>0)
                  connString = storByConn.FirstOrDefault().Value;
            }
            catch (Exception exe)
            {
                throw;
            }
            if (string.IsNullOrEmpty(connString))
                connString = ConnStringDictionary.Where(o => o.connDataBaseEnmu == ConnDataBaseEnmu.Master).FirstOrDefault().ConnString;

            return connString;
        }
        
        // Transactions - Not in yet
        //[DiagnosticName("Microsoft.EntityFrameworkCore.TransactionStarted")]
        //public void OnTransactionStarted()
        //{
        //    // Available: DbConnection connection, Guid connectionId, DbTransaction transaction
        //}

        //[DiagnosticName("Microsoft.EntityFrameworkCore.TransactionCommitted")]
        //public void OnTransactionCommitted()
        //{
        //    // Available: DbConnection connection, Guid connectionId, DbTransaction transaction, long startTimestamp, long currentTimestamp
        //}

        //[DiagnosticName("Microsoft.EntityFrameworkCore.TransactionRolledback")]
        //public void OnTransactionRolledback()
        //{
        //    // Available: DbConnection connection, Guid connectionId, DbTransaction transaction, long startTimestamp, long currentTimestamp
        //}

        //[DiagnosticName("Microsoft.EntityFrameworkCore.TransactionDisposed")]
        //public void OnTransactionDisposed()
        //{
        //    // Available: DbConnection connection, Guid connectionId, DbTransaction transaction
        //}

        //[DiagnosticName("Microsoft.EntityFrameworkCore.TransactionError")]
        //public void OnTransactionError()
        //{
        //    // Available: DbConnection connection, Guid connectionId, DbTransaction transaction, string action, Exception exception, long startTimestamp, long currentTimestamp
        //}
    }
}
